/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

////////////////////////////////////////////////////////////////
//
//	File:	CValue.cpp
//  Author:	George Harth
//
//
#include "stdafx.h"
#include "DConfigGlobals.h"
#include "CValue.h"
#include <math.h>
#include "CImplementer.h"

///////////////////////////////////
#if defined(USING_MFC) && defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////

#pragma warning(disable:4800)	// forcing int to bool warning
//#pragma warning(disable:4273)	// inconsistent dll linkage warning nonsense
//#define DLL_EXPORT __declspec( dllexport )

/*#ifdef _DEBUG
#define GARBAGE_COLLECT __GARBAGE_COLLECT()
void VALUE::__GARBAGE_COLLECT()
{
	if (type == stString) StringValue.Empty();
	else if ((type >= FIRST_PASS_BY_REF) && (ci != NULL)) {
		MUTEX_LOCK(ci->m_RefMutex);
		if (--(ci->m_RefCount) <= 0) {
			delete ci;
		}
		else MUTEX_UNLOCK(ci->m_RefMutex);
	}
	// Since GARBAGE_COLLECT is always only called when the current
	// ci is about to be overwritten with new data (or in the VALUE class
	// destructor where we lose the pointer anyway, it is fine to set
	// the ci to NULL here.  It could be argued that setting this here is
	// useless, but it makes me feel safer cleaning up pointers for potential
	// calls to delete (I'd rather give it a safe NULL than garbage).
	ci = NULL;	// If not here, at least do this after the above delete ci;
}
#else*/
#define GARBAGE_COLLECT	\
	if (type == stString) StringValue.Empty();	\
	else if ((type >= FIRST_PASS_BY_REF) && (ci != NULL)) {	\
		MUTEX_LOCK(ci->m_RefMutex);			\
		if (--(ci->m_RefCount) <= 0) {		\
			delete ci;						\
		}									\
		else MUTEX_UNLOCK(ci->m_RefMutex);	\
	}										\
	ci = NULL;	// See above comments...
//#endif

/*void VALUE::DecRefCount()
{
	// Use this only if you are really sure about what you need to do!
	OutputDebugString(_T("*** VALUE reference count being manually decremented!\n"));
	MUTEX_LOCK(ci->m_RefMutex);
	if (--(ci->m_RefCount) <= 0) {
		delete ci;
		ci = NULL;
		OutputDebugString(_T("*** VALUE::ci being destroyed by manual decrement!\n"));
	}
	else MUTEX_UNLOCK(ci->m_RefMutex);
}*/

//==========================================================

// This container class makes it possible for stVoid values to be
// safely garbage collected.  When garbage collection occurs, we
// call the user defined deletion function to clean up.
struct CVoidContainer : public CImplementer {
	void* m_pVoid;
	void (*m_FreeVoid)(void* pVoid);
	TString m_RTTI;

	CVoidContainer(void* pVoid, void (*freeVoid)(void* pVoid), TString rttiName) {
		m_pVoid = pVoid;
		m_FreeVoid = freeVoid;
		if (rttiName.IsEmpty()) m_RTTI = _T("void");
		else m_RTTI = rttiName;
	}

	virtual ~CVoidContainer() {
		if (m_FreeVoid != NULL) {
			m_FreeVoid(m_pVoid);
		}
	};
};

// The rest of the Alloc functions are inlined in header
VALUE AllocVoid(void* ptr, void (*freeVoid)(void* pVoid), TString rttiName)
{
	VALUE val;
	val.ForceCI(stVoid, new CVoidContainer(ptr, freeVoid, rttiName), false);
	return val;
}

//==========================================================
// First, a few serializing helper functions

U8* SerializeInInt(U8* inBuffer, int& val)
{
	// This should be a byte-endian independent
	val = (inBuffer[0] << 24) | (inBuffer[1] << 16) | (inBuffer[2] << 8) | inBuffer[3];
	return inBuffer + 4;
}

void SerializeOutInt(CFile* outFile, int val)
{
	// This should be a byte-endian independent storage
	ASSERT(sizeof(int) == 4);
	U8 store[4];
	store[0] = (val & 0xFF000000) >> 24;
	store[1] = (val & 0x00FF0000) >> 16;
	store[2] = (val & 0x0000FF00) >> 8;
	store[3] = (val & 0x000000FF);
	outFile->Write(store, 4);	// No conversion needed
}

/*U8* SerializeOutInt(U8* inBuffer, const int& val)
{
	// This should be a byte-endian independent storage
	ASSERT(sizeof(int) == 4);
	*(inBuffer++) = (val & 0xFF000000) >> 24;
	*(inBuffer++) = (val & 0x00FF0000) >> 16;
	*(inBuffer++) = (val & 0x0000FF00) >> 8;
	*(inBuffer++) = (val & 0x000000FF);
	return inBuffer;
}*/

U8* SerializeInFloat(U8* inBuffer, float& val)
{
	// This should be a byte-endian independent
	int x = (inBuffer[0] << 24) | (inBuffer[1] << 16) | (inBuffer[2] << 8) | inBuffer[3];
	memcpy(&val, &x, 4);
	return inBuffer + 4;
}

void SerializeOutFloat(CFile* outFile, float val)
{
	// This should be a byte-endian independent storage
	int x;
	memcpy(&x, &val, 4);

	U8 store[4];
	store[0] = (x & 0xFF000000) >> 24;
	store[1] = (x & 0x00FF0000) >> 16;
	store[2] = (x & 0x0000FF00) >> 8;
	store[3] = (x & 0x000000FF);
	outFile->Write(store, 4);	// No conversion needed
}

U8* SerializeInTString(U8* inBuffer, TString& string)
{
	long strLength;
	inBuffer = SerializeInInt(inBuffer, (int&)strLength);

	TCHAR* buf = string.GetBuffer(strLength);
#ifdef UNICODE
	for (int idx = 0; idx < strLength; ++idx) {
		// string is serialized using 8-bit chars, but
		// we may be reading into 16-bit unicode environment
		buf[idx] = (TCHAR)(*(inBuffer++));
	}
#else
	memcpy(buf, inBuffer, strLength);
	inBuffer += strLength;
#endif
	string.ReleaseBuffer(strLength);

	return inBuffer;
}

void SerializeOutTString(CFile* outFile, const TString& string)
{
	long length = string.GetLength();
	SerializeOutInt(outFile, length);

#ifndef UNICODE
	outFile->Write((LPCTSTR)string, length);	// No conversion needed
#else	// UNICODE defined
	// Would this be faster writting out a single char at a time without
	// having to allocate a char buffer?
	U8* charBuf = new U8[length];
	for (int idx = 0; idx < length; ++idx) {
		charBuf[idx] = (U8)(string[idx]);
	}
	outFile->Write(charBuf, length);
	delete[] charBuf;
#endif
}

//==========================================================

//VALUE::VALUE():ci(NULL), type(stUndefined){};

// Destructor must decrement the ci->m_RefCount if valid, delete ci if == zero
VALUE::~VALUE()
{
	GARBAGE_COLLECT;
}

void VALUE::ForceCI(SymbolTypes t, CImplementer* pCI, bool bIncRefCount)
{
	// Do not use ForceCI() unless you really, really need to.  And even then don't...
	// This could be done with a special VALUE constructor, but I don't
	// want it to be used without condsideration.
	// DO NOT call this using a CImplementer* that doesn't already belong
	// to a VALUE somewhere or else you will have to delete the ci yourself
	// because the m_RefCount will be one to high (or you could pre-decrement
	// the ci's refcount by one, or modify CImplementer to set it's refcount to
	// zero in the constructor instead of one, and modify the VALUE constructors
	// to increment it), or set the bIncRefCount arg to false.
	GARBAGE_COLLECT;
	type = t;
	ci = pCI;
	if (bIncRefCount && (ci != NULL)) {
		ci->m_RefCount++;
	}
}

VALUE VALUE::operator = (const VALUE& copy)
{
	//if (this == &copy) return *this;

	// !!! Strings are the only thing that don't currently
	// share the union with ci (bool, float, etc.)
	if ((copy.type != stString) && (ci == copy.ci)) {
		// This is already the same as the copy, so don't
		// do much of anything except make sure the type
		// is the same...  If we didn't do this, the code
		// in the else statement could delete the ci and
		// then assign the deleted ci to itself... BAD!!!
		type = copy.type;
	}
	else {
		GARBAGE_COLLECT;

		type = copy.type;
		if (type == stString) {
			StringValue = copy.StringValue;
			ci = NULL;
		}
		else {
			ASSERT(sizeof(bool) <= sizeof(int));
			ASSERT(sizeof(float) == sizeof(int));
			ASSERT(sizeof(ci) >= sizeof(int));
			ci = copy.ci;	// Since ci is in union with float, bool, and int, this copies all
			if ((ci != NULL) && (type >= FIRST_PASS_BY_REF)) {
				// Increase the garbage collected types usage counter
				ci->m_RefCount++;
			}
		}
	}
	return *this;
}

VALUE::VALUE(const VALUE& copy)
{
	// Copy contructor (the ci->m_RefCount gets incremented here for garbage collection)
	// Since this is a constructor, we don't have to worry about deleting a ci first
	type = copy.type;
	if (type == stString) {
		StringValue = copy.StringValue;
		ci = NULL;
	}
	else {
		ASSERT(sizeof(bool) <= sizeof(int));
		ASSERT(sizeof(float) == sizeof(int));
		ASSERT(sizeof(ci) >= sizeof(int));
		ci = copy.ci;	// Since ci is in union with float, bool, and int, this copies all
		if ((ci != NULL) && (type >= FIRST_PASS_BY_REF)) {
			// Increase the garbage collected types usage counter
			ci->m_RefCount++;
		}
	}
}

// This for stFrame, stArray, and stInteger
VALUE::VALUE(SymbolTypes t, int arg, const VALUE initEntries[])
{
	switch (t) {
		case stInteger:
			type = stInteger;
			IntegerValue = arg;
			break;
		case stArray:
			if (arg >= 0) {
				type = stArray;
				ci = new CArrayImpl(arg, initEntries);
			}
			else {
				InternalError(_T("Invalid array size in VALUE constuctor"));
			}
			break;
		case stFrame:
			type = stFrame;
			if (arg > 0) ci = new CFrameImpl(arg);
			else ci = new CFrameImpl(97);	// Default to hash table size of 97
			break;
		default:
			InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// For arrays, set all entries to single default value
VALUE::VALUE(SymbolTypes t, int arg, const VALUE initEntry)
{
	if (t == stArray) {
		// This contructor form should be moved into CArrayImpl...
		type = stArray;
		ci = new CArrayImpl(arg, NULL);
		for (int idx = 0; idx < arg; ++idx) {
			((CArrayImpl*)ci)->m_Values[idx] = initEntry;
		}
	}
	else {
		InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// For stBoolean and stNil
VALUE::VALUE(SymbolTypes t, bool arg)
{
	switch (t) {
		case stBoolean:
			type = stBoolean;
			BooleanValue = arg;
			break;
		case stNil:
			type = stNil;
			BooleanValue = false;
			break;
		default:
			InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// This for stSymbol, stString
VALUE::VALUE(SymbolTypes t, TString arg)
{
	switch (t) {
		case stString:
			type = stString;
			StringValue = arg;
			ci = NULL;	// Shouldn't be needed
			break;
		case stSymbol:
			if (!arg.IsEmpty()) {
				type = stSymbol;
				ci = new CSymbolImpl(arg);
			}
			else {
				InternalError(_T("Invalid symbol name == NULL"));
			}
			break;
		default:
			InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// This for stClass
VALUE::VALUE(SymbolTypes t, VALUE& baseClass, TString className)
{
	ASSERT((baseClass.type == stClass) || (baseClass.type == stUndefined));
	if (t == stClass) {
		type = stClass;
		ci = new CClassImpl(baseClass, className);
	}
	else {
		InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// This for stClassInstance
VALUE::VALUE(SymbolTypes t, VALUE& baseClass, CInterpreter* pCreater)
{
	ASSERT((baseClass.type == stClass) || (baseClass.type == stUndefined));
	if (t == stClassInstance) {
		type = stClassInstance;
		ci = new CClassInstImpl(baseClass, pCreater);
	}
	else {
		InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

// For stChunks...
VALUE::VALUE(SymbolTypes t, int length, const U8* data)
{
	if (t == stChunk) {
		type = stChunk;
		ci = new CChunkImpl(length, data);
	}
	else {
		InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}

VALUE::VALUE(SymbolTypes t, TString text, int encoding, int itype)
{
	if (t == stChunk) {
		type = stChunk;
		ci = new CChunkImpl(text, encoding, itype);
	}
	else {
		InternalError(_T("Invalid symbol type for VALUE constuctor"));
	}
}


VALUE::VALUE(CClassImpl* lpCoreClass)
{
	type = stClass;
	ci = lpCoreClass;
}

VALUE::VALUE(SymbolTypes t, VALUE readFunc, VALUE writeFunc, VALUE vInitial)
{
	if (t != stProperty) {
		InternalError(_T("Invalid type in property VALUE constructor"));
	}
	else {
		type = stProperty;
		ci = new CPropertyImpl(readFunc, writeFunc, vInitial);
	}
}

// These two for stFunction
VALUE::VALUE(SymbolTypes t, CByteCode* code, TString paramList[], int paramCount)
{
	if (t != stFunction) {
		InternalError(_T("Invalid type in function VALUE constructor"));
	}
	else {
		type = stFunction;
		ci = new CFunctionImpl(code, paramList, paramCount);
	}
}

VALUE::VALUE(SymbolTypes t, VALUE (*lpCoreFunc)(VALUE* self, int argCount, VALUE argVals[],
			 CInterpreter* pInterp), TString moduleName)
{
	if (t != stFunction) {
		InternalError(_T("Invalid type in function VALUE constructor"));
	}
	else {
		type = stFunction;
		ci = new CFunctionImpl(lpCoreFunc, moduleName);
	}
}

//==========================================================

VALUE VALUE::Map(VALUE (*mapFunc)(VALUE& arrayItem))
{
	// Given this VALUE which is an array, create and return
	// another array the same size that contains the resulting
	// values from this array after being passed through mapFunc

	VALUE result;

	if (type == stArray) {
		CArrayImpl* a = (CArrayImpl*)ci;
		result = AllocArray(a->m_Length);
		for (int idx = 0; idx < a->m_Length; ++idx) {
			result.Array()->m_Values[idx] = mapFunc(a->m_Values[idx]);
		}
	}
	else if (type == stFrame) {

	}
	else {
		InternalError(_T("map(array, func(arrayItem)) undefined for type: ") + TypeString());
	}

	return result;
}

VALUE VALUE::Map(VALUE (*mapFunc)(VALUE& key, VALUE& value))
{
	// Given this VALUE which is a frame, create and return
	// another frame the same size that contains the resulting
	// values from this frame after being passed through mapFunc

	VALUE result;

	if (type == stFrame) {
		result = AllocFrame();
		FindNextInfo fni;
		VALUE key, value;
		while (FindNext(fni, key, value)) {
			value = mapFunc(key, value);
			if (value.Type() != stNil) {
				result.Set(key, value);
			}
		}
	}
	else {
		InternalError(_T("map(frame, func(key, value)) undefined for type: ") + TypeString());
	}

	return result;
}

VALUE VALUE::Select(bool (*selectFunc)(VALUE& array))
{
	// Given this VALUE which is an array, create and return
	// another array composed of only those values where
	// selectFunc() returns true

	VALUE result;

	if (type == stArray) {
		CArrayImpl* a = (CArrayImpl*)ci;
		int count = 0;

		// Create an array large enough to hold maximum number
		// of values
		VALUE* selected = new VALUE[a->m_Length];

		// Loop through all values and only copy the ones
		// where selectFunc() returns true
		for (int idx = 0; idx < a->m_Length; ++idx) {
			if (selectFunc(a->m_Values[idx])) {
				selected[count++] = a->m_Values[idx];
			}
		}

		// Create the final resulting array of selected values only
		result = AllocArray(count, selected);
		delete[] selected;
	}
	else {
		InternalError(_T("VALUE::Select undefined for type"));
	}

	return result;
}

static inline void Merge(VALUE array[], VALUE tempArray[], int lpos, int rpos, int rend,
						 bool (*compareFunc)(VALUE& first, VALUE& second))
{
	int lend = rpos - 1;
	int tmppos = lpos;
	int numelements = rend - lpos + 1;

	while (lpos <= lend  && rpos <= rend) {
		if (compareFunc(array[lpos], array[rpos])) {
			tempArray[tmppos++] = array[lpos++];
		}
		else {
			tempArray[tmppos++] = array[rpos++];
		}
	}

	while (lpos <= lend) tempArray[tmppos++] = array[lpos++];

	while (rpos <= rend) tempArray[tmppos++] = array[rpos++];

	for (int idx = 0; idx < numelements; ++idx, --rend) {
		array[rend] = tempArray[rend];
	}
}

static void MergeSort(VALUE array[], VALUE tempArray[], int left, int right,
					   bool (*compareFunc)(VALUE& first, VALUE& second))
{
	if (left < right) {
		int center = (left + right) >> 1;
		MergeSort(array, tempArray, left, center, compareFunc);
		MergeSort(array, tempArray, center + 1, right, compareFunc);
		Merge(array, tempArray, left, center + 1, right, compareFunc);
	}
}

VALUE VALUE::Sort(bool (*compareFunc)(VALUE& first, VALUE& second))
{
	VALUE result;

	if (type == stArray) {
		CArrayImpl* array = (CArrayImpl*)ci;
		result = AllocArray(array->m_Length, array->m_Values);
		VALUE* tempArray = new VALUE[array->m_Length];
		MergeSort(result.Array()->m_Values, tempArray, 0, array->m_Length - 1, compareFunc);
		delete[] tempArray;
	}
	else {
		InternalError(_T("Sort undefined for type"));
	}

	return result;
}

//==========================================================

void VALUE::SerializeIn(TString filename)
{
	TFile inFile;
	inFile.Open(filename, TFile::read);
	SerializeIn(inFile);
	inFile.Close();
}

void VALUE::SerializeIn(TFile& inFile)
{
	ASSERT(sizeof(int) == 4);
	ASSERT(sizeof(float) == 4);

	int fileSize = inFile.GetLength();
	if (fileSize < 4) {
		*this = VALUE();	// Force undefinition of this with garbage collection
		//RunTimeError("File is to short to be a valid serialized file");
		return;
	}

	U8* dataBuf = new U8[fileSize];
	inFile.Read(dataBuf, fileSize);

	if ((dataBuf[0] != 'd' + 128) || (dataBuf[1] != 's' + 128) || (dataBuf[2] != FILE_VERSION_BYTE)) {
		delete[] dataBuf;
		*this = VALUE();	// Force undefinition of this with garbage collection
		RunTimeError(_T("Serialized file header is not as expected"));
	}
	else {
		SerializeIn(&dataBuf[3]);	// Skip 3 byte file header
		delete[] dataBuf;
	}
}

U8* VALUE::SerializeIn(U8* inBuffer)
{
	U8 valueType = *(inBuffer++);

	GARBAGE_COLLECT;

	switch (valueType) {
		case ST_STRING:
			ci = NULL;	// Might as well clear this out to be safe (strings don't use it)
			type = stString;
			inBuffer = SerializeInTString(inBuffer, StringValue);
			break;
		case ST_INTEGER:
			type = stInteger;
			inBuffer = SerializeInInt(inBuffer, IntegerValue);
			break;
		case ST_FLOAT:
			type = stFloat;
			inBuffer = SerializeInFloat(inBuffer, FloatValue);
			break;
		case ST_NIL:
			type = stNil;
			BooleanValue = false;
			break;
		case ST_BOOLEAN:
			type = stBoolean;
			BooleanValue = (*(inBuffer++)) == 1;
			break;
		case ST_ARRAY:
			type = stArray;
			inBuffer = CArrayImpl::SerializeIn(inBuffer, &ci);
			break;
		case ST_FRAME:
			type = stFrame;
			inBuffer = CFrameImpl::SerializeIn(inBuffer, &ci);
			break;
		case ST_FUNCTION:
			type = stFunction;
			inBuffer = CFunctionImpl::SerializeIn(inBuffer, &ci);
			break;
		case ST_SYMBOL:
			type = stSymbol;
			inBuffer = CSymbolImpl::SerializeIn(inBuffer, &ci);
			break;
		case ST_CHUNK:
			type = stChunk;
			inBuffer = CChunkImpl::SerializeIn(inBuffer, &ci);
			break;
		case ST_NOP:
			// Just padding, ignor it
			break;
		default:
			InternalError(_T("VALUE can't unserialze object, possibly invalid data file."));
	}

	return inBuffer;	// Which now points to the start of the next object
}

#ifdef CRYPTO_SUPPORT
bool VALUE::SerializeInMemory(U8* pData, int length)
{
	ASSERT(sizeof(int) == 4);
	ASSERT(sizeof(float) == 4);
	bool result = false;
	if (length < 4) {
		*this = VALUE();	// Force undefinition of this with garbage collection
	}
	else if ((pData[0] != 'd' + 128) || (pData[1] != 's' + 128) || (pData[2] != FILE_VERSION_BYTE)) {
		*this = VALUE();	// Force undefinition of this with garbage collection
	}
	else {
		_TRY {
			SerializeIn(&pData[3]);	// Skip 3 byte file header
			result = true;
		}
		_CATCH_ALL {
			result = false;
		}
	}
	return result;
}

bool VALUE::SerializeInMemory(VALUE vChunkIn)
{
	return SerializeInMemory(vChunkIn.GetChunkData(), vChunkIn.GetChunkByteCount());
}

VALUE VALUE::SerializeOutMemory()
{
	// Used to encrypt a complete VALUE at runtime
	ASSERT(sizeof(int) == 4);
	ASSERT(sizeof(float) == 4);

	CMemFile file(4096);
	U8 header[3] = {'d' + 128, 's' + 128, FILE_VERSION_BYTE};
	file.Write(header, 3);

	VALUE vResult = AllocNil();
	_TRY {
		SerializeOut(&file);
		// Now let's add some padding bytes.
		// A hack for blowfish encrypting since blowfish won't encrypt the
		// last mod 8 bytes of data.  So add 8 bytes of padding to ensure
		// all data worth encrypting is in fact encrypted.
		U8 padding[8] = {ST_NOP, ST_NOP, ST_NOP, ST_NOP, ST_NOP, ST_NOP, ST_NOP, ST_NOP};
		file.Write(padding, 8);

		int bytes = file.GetLength();
		BYTE* pMem = file.Detach();
		vResult = AllocChunk(bytes, pMem);
		free(pMem);
	}
	_CATCH_ALL {
		vResult = AllocNil();
	}
	return vResult;
}
#endif // CRYPTO_SUPPORT

void VALUE::SerializeOut(TString filename)
{
	ASSERT(sizeof(int) == 4);
	ASSERT(sizeof(float) == 4);

	//TFile file;
	//file.Open(filename, TFile::write | TFile::create);
	CFile file;
	file.Open(filename, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite | CFile::typeBinary);
	U8 header[3] = {'d' + 128, 's' + 128, FILE_VERSION_BYTE};
	file.Write(header, 3);
	SerializeOut(&file);
	file.Close();
}

void VALUE::SerializeOut(CFile* outFile)
{
	U8 header[2];

	switch (type) {
		case stString:
			header[0] = ST_STRING;
			outFile->Write(header, 1);
			SerializeOutTString(outFile, StringValue);
			break;
		case stInteger:
			header[0] = ST_INTEGER;
			outFile->Write(header, 1);
			SerializeOutInt(outFile, IntegerValue);
			break;
		case stFloat:
			header[0] = ST_FLOAT;
			outFile->Write(header, 1);
			SerializeOutFloat(outFile, FloatValue);
			break;
		case stBoolean:
			header[0] = ST_BOOLEAN;
			header[1] = BooleanValue ? 1 : 0;
			outFile->Write(header, 2);
			break;
		case stNil:
			header[0] = ST_NIL;
			outFile->Write(header, 1);	// No data needed, just ST_NIL marker
			break;
		case stArray:
		case stFrame:
		case stFunction:
		case stSymbol:
		case stChunk:
		//case stClass:
		//case stClassInstance:
			ci->SerializeOut(outFile);
			break;
		default:
			InternalError(_T("Interpreter can't serialize objects of this type: %s"),
				(LPCTSTR)TypeString());
	}
}

//==========================================================

VALUE VALUE::Clone() const
{
	switch (type) {
		// These are all pass by value, so just return this
		case stString:
		case stInteger:
		case stFloat:
		case stBoolean:
		case stNil:
		case stUndefined:
			return *this;

		// These are pass by refernce, so ask the ci to give use what we need
		case stArray:
		case stFrame:
		case stSymbol:
		case stFunction:
		case stClass:
		case stClassInstance:
		case stChunk:
			return ci->__clone__();

		default:
			InternalError(_T("Interpreter can't clone this object"));
			return AllocNil();
	}
}

VALUE VALUE::DeepClone() const
{
	if (type < FIRST_PASS_BY_REF) {
		// These are all pass by value, so just return this
		return *this;
	}
	else {
		// These are pass by refernce, so ask the ci to give use what we need
		return ci->__deepclone__();
	}
}

bool VALUE::Isa(TString typeName)
{
	if (type != stClassInstance) {
		return (typeName.CompareNoCase(TypeString()) == 0);
	}
	else {
		return ((CClassInstImpl*)ci)->m_BaseClass.Class()->IsA(typeName);
	}
}

TString VALUE::TypeString() const
{
	switch (type) {
		case stInteger:			return _T("integer");
		case stString:			return _T("string");
		case stBoolean:			return _T("boolean");
		case stNil:				return _T("nil");
		case stUndefined:		return _T("undefined");
		case stFloat:			return _T("float");
		case stArray:			return _T("array");
		case stSymbol:			return _T("symbol");
		case stFrame:			return _T("frame");
		case stFunction:		return _T("function");
		case stChunk:			return _T("chunk");
		case stClass:			return _T("class");
		case stClassInstance:	return ((CClassInstImpl*)ci)->m_ClassName;
		case stVoid:			return ((CVoidContainer*)ci)->m_RTTI;
	}
	InternalError(_T("Missing TypeString() conversion for type"));
	return _T("");
}

int VALUE::Integer() const
{
	int result;
	if (type == stInteger) result = IntegerValue;
	else if (type == stFloat) result = (int)FloatValue;
	else if (type == stBoolean) result = BooleanValue ? 1 : 0;
	else if (type == stString) result = _ttoi(StringValue);	// Returns zero if can't be done
	else if (type == stNil) result = 0;
	else if (type == stUndefined) result = 0;
	else InternalError(_T("Can't convert type to integer: %s"), (LPCTSTR)TypeString());
	return result;
}

float VALUE::Float() const
{
	float result;
	if (type == stFloat) result = FloatValue;
	else if (type == stInteger) result = (float)IntegerValue;
	else if (type == stNil) result = 0.0f;
	else if (type == stUndefined) result = 0.0f;
	else if (type == stBoolean) result = BooleanValue ? 1.0f : 0.0f;
	else if (type == stString) result = (float)_tcstod(StringValue, NULL);	// Returns zero if can't be done
	else InternalError(_T("Invalid Float conversion"));
	return result;
}

bool VALUE::Boolean() const
{
	switch (type) {
		case stBoolean:		return BooleanValue;
		case stInteger:		return IntegerValue == 0 ? false : true;
		case stFloat:		return FloatValue == 0.0f ? false : true;
		case stString:		return StringValue.IsEmpty() ? false : true;
		case stVoid: case stFunction: case stChunk:
			return true;
		case stNil: case stUndefined:
			return false;
		// George is planning to complicate this someday
		case stClass:		  return true;
		case stClassInstance: return true;
		default:
			if ((type >= FIRST_PASS_BY_REF) && (ci != NULL)) return ci->__boolean__();
	}

	InternalError(_T("Don't know how to convert type to boolean:"), (LPCTSTR)TypeString());
	return false;
}

TString VALUE::String() const
{
	TString result;
	switch (type) {
		case stString:
			return StringValue;
		case stInteger:
			result.Format(_T("%d"), IntegerValue);
			return result;
		case stBoolean:
			return BooleanValue ? _T("true") : _T("false");
		case stFloat:
/*#ifdef __WINCE__
			{
				char buf[64];
				_gcvt(FloatValue, 8, buf);
				result.Empty();
				int idx = 0;
				while (buf[idx] != 0) {
					result += (TCHAR)buf[idx++];
				}
				if (result[result.GetLength() - 1] == '.') {
					result += (TCHAR)'0'; // Quick hack
				}
			}
#else*/
			{
			//LPTSTR p = result.GetBuffer(64);
			//_stprintf(p, _T("%g"), FloatValue);
			//result.ReleaseBuffer();
			}
			result.Format(_T("%g"), FloatValue);
//#endif
			return result;
		case stNil:
		case stUndefined:
			return _T("");
		case stVoid:
			result.Format(_T("<void=%s>"), TypeString());
			return result;
		default:
			if (ci != NULL) {
				result = ci->__string__();
			}
			else {
				result.Empty();
			}
			return result;
	}
}

VALUE* VALUE::FindValuePtr(const VALUE& key, bool bCreateOK)
{
	switch (type) {
		case stFrame:
		case stArray:
		case stClass:
		case stClassInstance:
			return ci->FindValuePtr(key, bCreateOK);
		case stNil:
		case stUndefined:
			return NULL;
		default:
			InternalError(_T("Invalid type to call VALUE::FindValuePtr() on:"), (LPCTSTR)TypeString());
	}
	return NULL;
}

//////////////////////////

void* VALUE::Void()
{
	if (type == stVoid) {
		if (ci == NULL) return NULL;
		else return ((CVoidContainer*)ci)->m_pVoid;
	}
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a void."), (LPCTSTR)TypeString());
	return NULL;
}

CArrayImpl* VALUE::Array() const
{
	if (type == stArray) return static_cast<CArrayImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a array."), (LPCTSTR)TypeString());
	return NULL;
}

CChunkImpl* VALUE::Chunk() const
{
	if (type == stChunk) return static_cast<CChunkImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a chunk."), (LPCTSTR)TypeString());
	return NULL;
}

CFrameImpl* VALUE::Frame() const
{
	if (type == stFrame) return static_cast<CFrameImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a frame."), (LPCTSTR)TypeString());
	return NULL;
}

CSymbolImpl* VALUE::Symbol() const
{
	if (type == stSymbol) return static_cast<CSymbolImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a symbol."), (LPCTSTR)TypeString());
	return NULL;
}

CFunctionImpl* VALUE::Function() const
{
	if (type == stFunction) return static_cast<CFunctionImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a function."), (LPCTSTR)TypeString());
	return NULL;
}

CPropertyImpl* VALUE::Property() const
{
	if (type == stProperty) return static_cast<CPropertyImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a property."), (LPCTSTR)TypeString());
	return NULL;
}

CClassImpl* VALUE::Class() const
{
	if (type == stClass) return static_cast<CClassImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a class."), (LPCTSTR)TypeString());
	return NULL;
}

CClassInstImpl* VALUE::ClassInstance() const
{
	if (type == stClassInstance) return static_cast<CClassInstImpl*>(ci);
	else if (type == stUndefined) return NULL;
	InternalError(_T("Tried to misinterperet a %s as a class instance."), (LPCTSTR)TypeString());
	return NULL;
}

//===========================================================================

TString VALUE::__unparse__()
{
	// Here we return the item as it were to be parsed
	// For example, String() of "hello" returns hello (no quotes)
	//				__unparse__() of "hello" returns "hello" (with quotes)
	// For example, String() of stNil returns ""
	//				__unparse__() of stNil returns nil (the three characters, n, i, and l)
	TString result;
	switch (type) {
		case stInteger:
			result.Format(_T("%d"), IntegerValue);
			break;
		case stString:
			{
				// All of this extra effort is to convert double quotes into
				// accent marks (as an escape mechanism)
				LPCTSTR lpz = (LPCTSTR)StringValue;
				int length = _tcslen(lpz) + 2;		// Plus two for quote chars
				TCHAR* buf = result.GetBuffer(length);
				buf[0] = buf[length - 1] = '"';
				for (int idx = 1; idx < length - 1; ++idx, ++lpz) {
					buf[idx] = *lpz == '"' ? '`' : *lpz;
				}
				result.ReleaseBuffer(length);
			}
			break;
		case stBoolean:
			//result = BooleanValue ? _T("\"#T\"") : _T("\"#F\"");
			result = BooleanValue ? _T("true") : _T("false");
			break;
		case stNil:
			result = _T("nil");
			break;
		case stUndefined:
			result = _T("<undefined>");
			break;
		case stFloat:
			/*#ifdef __WINCE__
			{
				char buf[64];
				_gcvt(FloatValue, 8, buf);
				result.Empty();
				int idx = 0;
				while (buf[idx] != 0) {
					result += (TCHAR)buf[idx++];
				}
				if (result[result.GetLength() - 1] == '.') {
					result += (TCHAR)'0'; // Quick hack
				}
			}
			#else*/
			result.Format(_T("%g"), FloatValue);
			//#endif
			break;
		case stVoid:
			result.Format(_T("<void=%s>"), TypeString());
			break;
		default:
			if (ci != NULL) {
				result = ci->__unparse__();
			}
	}
	return result;
}

bool VALUE::FindNext(FindNextInfo& fni, VALUE& value)
{
	switch (type) {
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
		case stChunk:
			if (ci != NULL) {
				ci->__findnext__(fni, value);
			}
			else {
				ASSERT(false);
			}
			break;
		case stNil:
			value = AllocNil();
			fni.info = FIND_NEXT_FINISHED;
			break;
		default:
			InternalError(_T("FindNext(index) not supported on type: %s"), (LPCTSTR)TypeString());
	}
	return (bool)(fni.info != FIND_NEXT_FINISHED);
}

bool VALUE::FindNext(FindNextInfo& fni, VALUE& key, VALUE& value)
{
	switch (type) {
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
			if (ci != NULL) {
				ci->__findnext__(fni, key, value);
			}
			else {
				ASSERT(false);
			}
			break;
		case stNil:
			key = AllocNil();
			value = AllocNil();
			fni.info = FIND_NEXT_FINISHED;
			break;
		default:
			InternalError(_T("FindNext(key, value) not supported on type: %s"), (LPCTSTR)TypeString());
	}
	return (bool)(fni.info != FIND_NEXT_FINISHED);
}

void VALUE::Resize(int newLength, VALUE init)
{
	if ((type == stArray) || (type == stChunk)) {
		ci->__resize__(newLength, init);
	}
	else {
		InternalError(_T("Extend only works on array types"));
	}
}

void VALUE::Remove(const TString& key)
{
	if (type == stFrame) {
		((CFrameImpl*)ci)->Remove(key);
	}
	else if (type == stClassInstance) {
		((CClassInstImpl*)ci)->m_SelfSymTab.Frame()->Remove(key);
	}
	else {
		InternalError(_T("Remove only works on frame and class instance types"));
	}
}

int VALUE::Length() const
{
	int result = 0;
	switch (type) {
		case stString:
			result = StringValue.GetLength();
			break;
		case stNil:
			result = 0;
			break;
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
		case stChunk:
			if (ci != NULL) {
				result = ci->__length__();
			}
			else {
				ASSERT(false);
			}
			break;
		default:
			InternalError(_T("Length not supported for type: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

U8* VALUE::GetChunkData()
{
	if (type == stChunk) {
		return ((CChunkImpl*)ci)->m_Data;
	}
	else {
		ASSERT(false);
		return NULL;
	}
}

int VALUE::GetChunkByteCount() const
{
	if (type == stChunk) {
		return ((CChunkImpl*)ci)->m_Length;
	}
	else {
		ASSERT(false);
		return 0;
	}
}

bool VALUE::CopyChunkProperties(VALUE vChunk)
{
	if (type != stChunk) {
		ASSERT(false);
		return false;
	}

	int i = vChunk.Chunk()->m_IndexType;
	int e = vChunk.Chunk()->m_EncodingType;
	int d = vChunk.Chunk()->m_DimensionCount;
	int* newpdc = NULL;
	if (d > 1) {
		int* pdc = vChunk.Chunk()->m_Dimensions;
		newpdc = new int[d];
		for (int idx = 0; idx < d; ++idx) {
			newpdc[idx] = pdc[idx];
		}
	}

	if (((CChunkImpl*)ci)->m_Dimensions != NULL) {
		delete[] ((CChunkImpl*)ci)->m_Dimensions;
	}

	((CChunkImpl*)ci)->m_IndexType = i;
	((CChunkImpl*)ci)->m_EncodingType = e;
	((CChunkImpl*)ci)->m_DimensionCount = d;
	((CChunkImpl*)ci)->m_Dimensions = newpdc;

	return true;
}

//================

VALUE VALUE::operator[] (const VALUE& arg) const
{
	switch (type) {
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
			if (ci != NULL) return (*ci)[arg];
			else {return VALUE();}
			break;
		default:
			ASSERT(false);
			return VALUE();	// else return stUndefined empty VALUE
	}
}

//================

VALUE VALUE::Get(const VALUE& arg)
{
	switch (type) {
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
		case stChunk:
			if (ci != NULL) return ci->__get__(arg);
			else {ASSERT(false); return VALUE();}
			break;
		case stString:
			if (arg.type == stInteger) /*&& (arg.Integer() <= StringValue.GetLength()) &&
				(arg.Integer() >= 1))*/
			{
				int idx = arg.Integer() - 1;
				int strLen = StringValue.GetLength();
				if (idx < 0) {
					idx = strLen + idx + 1;
				}

				if ((idx >= strLen) || (idx < 0)) return _T("");
				else return TString(StringValue[idx]);
			}
			else if ((arg.Type() == stArray) && (arg.Length() == 2) &&
				(arg[1].Type() == stInteger) &&	(arg[2].Type() == stInteger))
			{
				// Returning a range like myString[2->5]
				int startIdx = arg[1].Integer();
				int endIdx = arg[2].Integer();
				int strLen = StringValue.GetLength();

				if (startIdx < 0) startIdx = strLen + startIdx + 1;
				if (endIdx < 0) endIdx = strLen + endIdx + 1;
				if (startIdx < 1) startIdx = 1;
				if (endIdx > strLen) endIdx = strLen;

				int size = endIdx - startIdx + 1;
				if (size < 0) {
					RunTimeError(_T("Start index of range occurs after end index"));
				}
				else {
					return StringValue.Mid(startIdx - 1, size);
				}
			}
			return _T("");
		case stNil:
			return *this;
		default:
			ASSERT(false);
			return VALUE();	// else return stUndefined empty VALUE
	}
}

//================

void VALUE::Set(const VALUE& indexer, const VALUE& arg)
{
	switch (type) {
		case stArray:
		case stFrame:
		case stClass:
		case stClassInstance:
		case stChunk:
			if (ci != NULL) {
				ci->__set__(indexer, arg);
			}
			else {
				ASSERT(false);
			}
			break;
	}
}

//==============================================================================
//
//	All these overloaded operators are kind of messy, but should make it very
//	simple to add an object system that supports op-overloading to the interpreter.
//

bool operator == (TString& first, VALUE& second);
bool operator != (TString& first, VALUE& second);

VALUE VALUE::operator+(VALUE& second)
{
	if (type == stNil) {
		return second;
	}
	else if (second.Type() == stNil) {
		return *this;
	}

	VALUE result;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.type = stFloat;
			result.FloatValue = (float)IntegerValue + second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.type = stFloat;
			result.FloatValue = FloatValue + (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to add types: %s + %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	result.type = type;
	switch(type) {
		case stInteger:
			result.IntegerValue = IntegerValue + second.IntegerValue;
			break;
		case stFloat:
			result.FloatValue = FloatValue + second.FloatValue;
			break;
		case stString:
			result.StringValue = StringValue + second.StringValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in addition: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator-(VALUE& second)
{
	if (type == stNil) {
		return second;
	}
	else if (second.type == stNil) {
		return *this;
	}

	VALUE result;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.type = stFloat;
			result.FloatValue = (float)IntegerValue - second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.type = stFloat;
			result.FloatValue = FloatValue - (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to subtract types: %s - %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	result.type = type;
	switch(type) {
		case stInteger:
			result.IntegerValue = IntegerValue - second.IntegerValue;
			break;
		case stFloat:
			result.FloatValue = FloatValue - second.FloatValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in subtraction: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator-()
{
	// Unary minus
	VALUE result;
	result.type = type;
	switch(type) {
		case stInteger:
			result.IntegerValue = -IntegerValue;
			break;
		case stFloat:
			result.FloatValue = -FloatValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in unary minus: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator * (VALUE& second)
{
	VALUE result;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.type = stFloat;
			result.FloatValue = (float)IntegerValue * second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.type = stFloat;
			result.FloatValue = FloatValue * (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to multiply types: %s * %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	result.type = type;
	switch(type) {
		case stInteger:
			result.IntegerValue = IntegerValue * second.IntegerValue;
			break;
		case stFloat:
			result.FloatValue = FloatValue * second.FloatValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in multiplication: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator / (VALUE& second)
{
	if (((second.type == stInteger) && (second.Integer() == 0)) ||
		((second.type == stFloat) && (second.Float() == 0.0f)))
	{
		RunTimeError(_T("Division by zero"));
	}

	VALUE result;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.type = stFloat;
			result.FloatValue = (float)IntegerValue / second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.type = stFloat;
			result.FloatValue = FloatValue / (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to divide types: %s / %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	result.type = type;
	switch(type) {
		case stInteger:
			result.type = stFloat;
			result.FloatValue = (float)IntegerValue / (float)second.IntegerValue;
			break;
		case stFloat:
			result.FloatValue = FloatValue / second.FloatValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in division: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator > (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue > second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue > (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to compare types: %s > %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue > second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue > second.FloatValue;
			break;
		case stString:
			result.BooleanValue = StringValue > second.StringValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in > comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator < (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue < second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue < (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to compare types: %s < %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue < second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue < second.FloatValue;
			break;
		case stString:
			result.BooleanValue = StringValue < second.StringValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in < comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator >= (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue >= second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue >= (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to compare types: %s >= %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue >= second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue >= second.FloatValue;
			break;
		case stString:
			result.BooleanValue = StringValue >= second.StringValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in >= comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator <= (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue <= second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue <= (float)second.IntegerValue;
			return result;
		}
		else RunTimeError(_T("Unable to compare types: %s <= %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue <= second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue <= second.FloatValue;
			break;
		case stString:
			result.BooleanValue = StringValue <= second.StringValue;
			break;
		default:
			RunTimeError(_T("Unsupported type in <= comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator != (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue != second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue != (float)second.IntegerValue;
			return result;
		}
		else if ((type == stBoolean) || (type == stNil) ||
			(second.type == stBoolean) || (second.type == stNil)) {
			return Boolean() != second.Boolean();
		}
		else if (type == stString) {
			if (second.type == stNil) {
				// Since a nil == false, we do this hack (else edit operator== above)
				return Boolean();
			}
			else {
				return StringValue != second;
			}
		}
		else RunTimeError(_T("Unable to compare types: %s <> %s"), (LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue != second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue != second.FloatValue;
			break;
		case stBoolean:
			result.BooleanValue = BooleanValue != second.BooleanValue;
			break;
		case stString:
			result.BooleanValue = StringValue != second.StringValue;
			break;
		case stSymbol:
			result.BooleanValue = String().CompareNoCase(second.String()) != 0;
			break;
		case stNil:
			result.BooleanValue = false;	// (nil != nil) == false
			break;
		case stClass:
			// Compare type strings?
			result.BooleanValue = TypeString().CompareNoCase(second.TypeString()) != 0;
			break;
		case stArray:
		case stFrame:
		case stClassInstance:
			// Eventually check for class instance a overload first (__equal__ ?)
			result.BooleanValue = ci != second.ci;
			break;
		case stChunk:
			if (GetChunkByteCount() != second.GetChunkByteCount()) {
				result .BooleanValue = true;
			}
			else {
				result.BooleanValue = (bool)(memcmp(GetChunkData(), second.GetChunkData(), GetChunkByteCount()) != 0);
			}
			break;
		default:
			RunTimeError(_T("Unsupported type in <> comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator == (VALUE& second)
{
	VALUE result;
	result.type = stBoolean;
	if (type != second.type) {
		if ((type == stInteger) && (second.type == stFloat)) {
			result.BooleanValue = (float)IntegerValue == second.FloatValue;
			return result;
		}
		else if ((type == stFloat) && (second.type == stInteger)) {
			result.BooleanValue = FloatValue == (float)second.IntegerValue;
			return result;
		}
		else if ((type == stBoolean) || (type == stNil) ||
			(second.type == stBoolean) || (second.type == stNil)) {
			return Boolean() == second.Boolean();
		}
		else if (type == stString) {
			if (second.type == stNil) {
				// Since a nil == false, we do this hack (else edit operator== above)
				result = Boolean();
				result.BooleanValue = !result.BooleanValue;
				return result;
			}
			else {
				return StringValue == second;
			}
		}
		else {
			RunTimeError(_T("Unable to compare types: %s = %s"),
				(LPCTSTR)TypeString(), (LPCTSTR)second.TypeString());
		}
	}

	switch(type) {
		case stInteger:
			result.BooleanValue = IntegerValue == second.IntegerValue;
			break;
		case stFloat:
			result.BooleanValue = FloatValue == second.FloatValue;
			break;
		case stBoolean:
			result.BooleanValue = BooleanValue == second.BooleanValue;
			break;
		case stString:
			result.BooleanValue = StringValue == second.StringValue;
			break;
		case stSymbol:
			result.BooleanValue = String().CompareNoCase(second.String()) == 0;
			break;
		case stNil:
			result.BooleanValue = true;	// (nil == nil) == true
			break;
		case stClass:
			// Compare type strings?
			result.BooleanValue = TypeString().CompareNoCase(second.TypeString()) == 0;
			break;
		case stArray:
		case stFrame:
		case stClassInstance:
			// Eventually check for class instance a overload first (__equal__ ?)
			result.BooleanValue = ci == second.ci;
			break;
		case stChunk:
			if (GetChunkByteCount() != second.GetChunkByteCount()) {
				result .BooleanValue = false;
			}
			else {
				result.BooleanValue = (bool)(memcmp(GetChunkData(), second.GetChunkData(), GetChunkByteCount()) == 0);
			}
			break;
		default:
			RunTimeError(_T("Unsupported type in Boolean = comparison: %s"), (LPCTSTR)TypeString());
	}
	return result;
}

VALUE VALUE::operator && (VALUE& second)
{
	return Boolean() && second.Boolean();
}

VALUE VALUE::operator || (VALUE& second)
{
	return Boolean() || second.Boolean();
}

VALUE VALUE::operator!()
{
	return !Boolean();	// Boolean Not
}

//==============================================
// Type promoting versions

bool operator == (TString& first, VALUE& second)
{
	bool bResult;
	switch (second.Type()) {
		case stBoolean:
			bResult = second.Boolean() != (bool)first.IsEmpty();
			break;
		case stString:
			bResult = first == second.String();
			break;
		default:
			RunTimeError(_T("Unable to compare types: string = %s"), (LPCTSTR)second.TypeString());
	}
	return bResult;
}

bool operator != (TString& first, VALUE& second)
{
	bool bResult;
	switch (second.Type()) {
		case stBoolean:
			bResult = second.Boolean() == (bool)first.IsEmpty();
			break;
		case stString:
			bResult = first != second.String();
			break;
		default:
			RunTimeError(_T("Unable to compare types: string <> %s"), (LPCTSTR)second.TypeString());
	}
	return bResult;
}

//==============================================
